package com.example.exceldemo.config;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.TemplateExportParams;
import cn.afterturn.easypoi.word.WordExportUtil;
import com.example.exceldemo.enuns.FileType;
import com.example.exceldemo.exception.ServiceException;
import com.example.exceldemo.utils.FileUtils;
import com.example.exceldemo.utils.PdfUtil;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.docx4j.Docx4J;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @ClassName WordUtil
 * @Description 描述:easypoi导出工具类
 * @Version 2.0
 **/
@Component
public class WordUtil {
    private static final Logger log = LoggerFactory.getLogger(PdfUtil.class);


    private final static String TEMPORARY_DIRECTORY = "/data/file/park/temPath";


    @Autowired
    private ResourceLoader resourceLoader;

    /**
     * @param temPath     模版路径
     * @param params
     * @param temFileName 存放临时文件的名字：模版名字.docx
     * @return
     */
    public byte[] exportPdfByte(String temPath, Map<String, Object> params, String temFileName) {
        log.info("开始生成doc文件");
        //替换自己服务的存储目录
        try {
            String tempDirPath = TEMPORARY_DIRECTORY;
            File tempDir = new File(tempDirPath);
            ensureDirectoryExists(tempDir);
            //linux环境不行
            //String absolutePath = resourceLoader.getResource("classpath:" + temPath).getFile().getAbsolutePath();
            //获取模板绝对路径，如果是频繁变更模版，使用完建议删临时模版
            String path = getTemplatePath(tempDir, temPath, temFileName);
            return exportPdfAbsolutePath(path, params);
        } catch (Exception e) {
            throw new ServiceException("导出获取pdf字节异常：" + e.getMessage());
        }
    }

    /**
     * 获取word文件字节数组
     *
     * @param templatePath 模版路径
     * @param tempFileName 存放临时文件的模版名字：例：1.docx
     * @return
     * @throws IOException
     */
    public byte[] exportWordByte(Map<String, Object> params, String templatePath, String tempFileName) {
        try {
            log.info("开始生成doc文件");
            File tempDir = new File(TEMPORARY_DIRECTORY);
            ensureDirectoryExists(tempDir);
            String path = getTemplatePath(tempDir, templatePath, tempFileName);

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            log.info("开启读取doc模板,最终模版路径：" + path);
            //path必须是绝对路径
            XWPFDocument doc = WordExportUtil.exportWord07(path, params);
            log.info("读取doc模板结束.....");
            doc.write(byteArrayOutputStream);
            byte[] documentBytes = byteArrayOutputStream.toByteArray();
            return documentBytes;
        } catch (Exception e) {
            log.error("生成doc文件失败", e);
            throw new ServiceException("生成doc文件失败", e);
        }
    }

    /**
     * 导出PDF
     *
     * @param templatePath word模板地址
     * @param params       替换的参数
     * @return pdf的绝对路径
     */
    public byte[] exportPdfAbsolutePath(String templatePath, Map<String, Object> params) {
        // 生成的wold文档文件名
        String woldFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".docx";
        //保存的文件路径名
        String saveDir = TEMPORARY_DIRECTORY + File.separator + "pdf";
        // 生成的pdf文件名
        String pdfFileName = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".pdf";
        // 导出wold文档 返回值为生成的wold文档全路径
        String word = exportWord(templatePath, saveDir, woldFileName, params);
        cn.hutool.core.lang.Assert.notNull(word, "word路径不能为空");
        // 自定义生成的pdf全路径
        String pdfPath = saveDir + File.separator + pdfFileName;
        // 导出pdf,同时删除生成的wold文档
        convertDocx2Pdf(word, pdfPath);
        //获取输出字节数组
        byte[] aByte = FileUtils.getByte(pdfPath);
        //删除pdf临时目录和文件
        delFileWord(saveDir, pdfFileName);
        //删除临时模版,如果频繁更新，建议删
        //delFile(templatePath);
        return aByte;
    }


    /**
     * 导出word
     * 模版变量中变量格式：{{foo}}
     *
     * @param templatePath word模板地址
     * @param saveDir      word文档保存的路径
     * @param fileName     文件名
     * @param params       替换的参数
     */
    public String exportWord(String templatePath, String saveDir, String fileName, Map<String, Object> params) {
        Assert.notNull(templatePath, "模板路径不能为空");
        Assert.notNull(saveDir, "临时文件路径不能为空");
        Assert.notNull(fileName, "导出文件名不能为空");
        Assert.isTrue(fileName.endsWith(".docx"), "word导出请使用docx格式");
        if (!saveDir.endsWith("/")) {
            saveDir = saveDir + File.separator;
        }

        File dir = new File(saveDir);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String savePath = saveDir + fileName;

        try {
            XWPFDocument doc = WordExportUtil.exportWord07(templatePath, params);
            FileOutputStream fos = new FileOutputStream(savePath);
            doc.write(fos);
            fos.flush();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return savePath;
    }

    /**
     * @param templatePath 模版存放静态资源位置
     * @param fileName
     * @param params
     * @param fileType
     * @param tempFileName 存放临时文件的模版名字：例：1.docx
     */
    public void exportWord(String templatePath, String fileName, Map<String, Object> params, FileType fileType, String tempFileName) {
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        String path = "";
        OutputStream out = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            log.info("开始生成" + fileType.getType() + "文件");
            //替换自己服务的存储目录
            String tempDirPath = TEMPORARY_DIRECTORY;
            File tempDir = new File(tempDirPath);
            ensureDirectoryExists(tempDir);
            //获取模板绝对路径，如果是频繁变更模版，使用完建议删临时模版
            path = getTemplatePath(tempDir, templatePath, tempFileName);
            log.info("开启读取模板,最终模版路径：" + path);
            //path必须是绝对路径
            try (XWPFDocument doc = WordExportUtil.exportWord07(path, params);) {
                log.info("读取模板结束.....");
                doc.write(bos);
                byte[] documentBytes = bos.toByteArray();
                // 设置响应头
                response.setContentType(fileType.getDesc());
                response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + fileType.getType());
                //设置字符编码
                response.setCharacterEncoding("UTF-8");
                out = response.getOutputStream();
                out.write(documentBytes);
            }

        } catch (Exception e) {
            log.error("导出" + fileType.getType() + "类型文档失败：" + e.getMessage());
            throw new ServiceException("导出" + fileType.getType() + "类型文档失败：" + e.getMessage());
        } finally {
            //这一步看具体需求，模版要不要删
//            if (StringUtils.isNotBlank(path))

//                delFile(path);
            try {
                if (out != null) {
                    out.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 导出Excel文件
     *
     * @param templatePath Excel模板路径
     * @param fileName     导出文件名
     * @param params       导出数据，以键值对形式存储
     * @param fileType     文件类型枚举，用于设置响应头
     * @param tempFileName 临时文件名，用于频繁变更模板时
     * @param sheetName    工作表名称数组，用于设置多个工作表
     */
    public void exportExcel(String templatePath, String fileName, Map<String, Object> params, FileType fileType, String tempFileName, String[] sheetName) {
        // 获取HttpServletResponse对象
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        String path = "";
        OutputStream out = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try {
            // 记录开始生成文件的日志
            log.info("开始生成" + fileType.getType() + "文件");
            // 替换自己服务的存储目录
            String tempDirPath = TEMPORARY_DIRECTORY;
            File tempDir = new File(tempDirPath);
            // 确保临时目录存在
            ensureDirectoryExists(tempDir);
            // 获取模板绝对路径，如果是频繁变更模版，使用完建议删临时模版
            path = getTemplatePath(tempDir, templatePath, tempFileName);
            // 设置为ture，代表支持多sheet模板导出
            TemplateExportParams exportParams = new TemplateExportParams(path, true);
            // 设置sheet名称
            if (sheetName != null && sheetName.length > 0)
                exportParams.setSheetName(sheetName);
            // 记录读取模板开始的日志
            log.info("开启读取模板,最终模版路径：" + path);
            // path必须是绝对路径
            try (Workbook doc = ExcelExportUtil.exportExcel(exportParams, params);) {
                // 记录读取模板结束的日志
                log.info("读取模板结束.....");
                // 将Excel数据写入字节数组输出流
                doc.write(bos);
                byte[] documentBytes = bos.toByteArray();
                // 设置响应头
                response.setContentType(fileType.getDesc());
                response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + fileType.getType());
                // 设置字符编码
                response.setCharacterEncoding("UTF-8");
                // 获取响应输出流
                out = response.getOutputStream();
                // 将字节数组写入响应输出流
                out.write(documentBytes);
            }

        } catch (Exception e) {
            // 记录导出失败的错误日志
            log.error("导出" + fileType.getType() + "类型文档失败：" + e.getMessage());
            // 抛出服务异常，提示导出失败
            throw new ServiceException("导出" + fileType.getType() + "类型文档失败：" + e.getMessage());
        } finally {
            // 根据需求决定是否删除模板
//            if (StringUtils.isNotBlank(path))

//                delFile(path);
            // 关闭输出流和字节数组输出流
            try {
                if (out != null) {
                    out.close();
                }
                if (bos != null) {
                    bos.close();
                }
            } catch (Exception ex) {
                // 打印关闭流时的异常堆栈信息
                ex.printStackTrace();
            }
        }
    }


    /**
     * 删除零时生成的文件和目录
     */
    public void delFileWord(String filePath, String fileName) {
        File file = new File(filePath + fileName);
        File file1 = new File(filePath);
        if (file.exists()) {
            file.delete();
            log.info("删除临时文件成功");
        }
        if (file1.exists()) {
            file1.delete();
            log.info("删除临时目录成功");
        }
    }

    /**
     * 删除临时文件
     */
    public void delFile(String fileName) {
        File file = new File(fileName);
        file.delete();
    }

    /**
     * @param wordPath word文件路径
     * @param pdfPath  pdf输出路径
     */
    public void convertDocx2Pdf(String wordPath, String pdfPath) {
        OutputStream os = null;
        InputStream is = null;
        if (pdfPath.endsWith("/")) {
            pdfPath = pdfPath + File.separator;
        }
        try {
            is = new FileInputStream(new File(wordPath));
            WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
            setFontMapper(mlPackage);
            os = new java.io.FileOutputStream(pdfPath);
            //docx4j  docx转pdf
            FOSettings foSettings = Docx4J.createFOSettings();
            foSettings.setWmlPackage(mlPackage);
            Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 删除word文档的地址
            File file = new File(wordPath);
            if (file != null && file.isFile() && file.exists())
                file.delete();
            try {
                if (is != null) {
                    is.close();
                }
                if (os != null) {
                    os.close();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    /**
     * 设置字体映射器
     * 此方法用于解决文档中中文字体的乱码问题
     * 它通过将文档中的中文字体映射到Java中对应的物理字体来确保字体的正确显示
     *
     * @param mlPackage WordprocessingMLPackage对象，用于处理Word文档
     */
    private static void setFontMapper(WordprocessingMLPackage mlPackage) throws Exception {
        // 创建字体映射器实例
        Mapper fontMapper = new IdentityPlusMapper();

        // 将常见的中文字体与其对应的物理字体进行映射
        fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
        fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
        fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
        fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
        fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
        fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
        fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
        fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
        fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
        fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
        fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
        fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
        fontMapper.put("等线", PhysicalFonts.get("SimSun"));
        fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
        fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
        fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
        fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
        fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
        fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
        fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
        fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
        fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
        fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
        fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
        //解决宋体（正文）和宋体（标题）的乱码问题
        PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
        PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));

        // 将字体映射器设置到Word文档处理对象中
        mlPackage.setFontMapper(fontMapper);
    }


    /**
     * 将指定路径的图像文件转换为Base64编码的字节数组
     *
     * @param path 图像文件的路径
     * @return 包含图像Base64编码的字节数组
     */
    public byte[] getImageBase64(String path) throws IOException {
        InputStream input = new FileInputStream(path);
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buf = new byte[1024];
        int numBytesRead = 0;

        while ((numBytesRead = input.read(buf)) != -1) {
            // 将读取的数据写入到输出流中
            output.write(buf, 0, numBytesRead);
        }

        // 将输出流中的数据转换为字节数组
        byte[] data = output.toByteArray();
        output.close();
        input.close();

        // 返回包含图像Base64编码的字节数组
        return data;
    }


    /**
     * 确保给定目录存在，如果不存在则尝试创建它
     *
     * @param directory 要确保存在的目录文件对象
     * @throws IOException 如果目录创建失败，则抛出IOException
     */
    private static void ensureDirectoryExists(File directory) throws IOException {
        // 检查目录是否已经存在
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                log.error("无法创建临时目录：" + directory.getAbsolutePath());
                throw new IOException("无法创建临时目录：" + directory.getAbsolutePath());
            }
        }
    }

    /***
     * 获取模版的绝对路径
     * @param tempDir
     * @param templatePath
     * @param tempFileName 模版名字+文件类型后缀 1.docx
     * @return
     * @throws IOException
     */
    private static String getTemplatePath(File tempDir, String templatePath, String tempFileName) throws IOException {
        //判断模版文件是否在临时目录，如果不存在则创建
        File templateFile = new File(tempDir, tempFileName);
        if (!templateFile.exists()) {
            log.info("模板文件不存在于临时目录，将从Classpath中复制。");
            copyTemplateToFile(templateFile, templatePath);
        }
        log.info("使用现有模板文件：" + templateFile.getAbsolutePath());
        return templateFile.getAbsolutePath();
    }

    /**
     * 在临时目录创建模版
     *
     * @param outputFile
     * @param templatePath
     * @throws IOException
     */
    private static void copyTemplateToFile(File outputFile, String templatePath) throws IOException {
        ClassPathResource resource = new ClassPathResource(templatePath);
        try (InputStream inputStream = resource.getInputStream();
             FileOutputStream outputStream = new FileOutputStream(outputFile)) {
            if (ObjectUtils.isEmpty(inputStream)) {
                throw new IOException("获取模板inputStream为空......");
            }
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            log.info("写入临时目录结束:" + outputFile.getParent());
        }
    }
}